#include <assert.h>

#include "helper.h"
#include "../include/spinlock.h"
#include "../include/semaphore.h"
#include "../include/rwlock.h"

namespace mix
{

struct RWLockImpl
{
#ifdef M_WIN
    volatile long rWaiting;
    volatile long rRunning;
    volatile long wWaiting;
    volatile long rwflag;
    SpinLock locker;
    Semaphore sem;
#else
    pthread_rwlock_t rwlock;
#endif
};

#define IMPL ((RWLockImpl*)_impl)
#define IMPL_VAR(rwi) RWLockImpl* rwi = (RWLockImpl*)_impl;

RWLock::RWLock() {
    RWLockImpl* rwi = new RWLockImpl();
    _impl = rwi;
#ifdef M_WIN
    rwi->rWaiting = 0;
    rwi->rRunning = 0;
    rwi->wWaiting = 0;
    rwi->rwflag = 0;
    if ( !rwi->sem.init())
        MIX_LOG( LOG_CRITICAL, "Semaphore in RWLock init failed.");
#else
    int ret = pthread_rwlock_init( &(rwi->rwlock), NULL);
    if ( ret)
        MIX_LOG( LOG_CRITICAL, "pthread_rwlock_init failed.[code: %d]", ret);
#endif
}

RWLock::~RWLock() {
    IMPL_VAR( rwi);
#ifdef M_WIN
    rwi->sem.close();
#else
    pthread_rwlock_destroy( &(rwi->rwlock));
#endif
    delete rwi;
    _impl = NULL;
}

void RWLock::rdLock() {
    IMPL_VAR( rwi);
#ifdef M_WIN
    rwi->locker.lock();
    if ( rwi->rwflag == 0) {
        rwi->rwflag = 1;
        ++rwi->rRunning;
        rwi->locker.unlock();
        return;
    }
    else if ( rwi->rwflag == 1) {
        ++rwi->rRunning;
        rwi->locker.unlock();
        return;
    }
    else {  // == 2
        ++rwi->rWaiting;
        rwi->locker.unlock();
        rwi->sem.wait();
        rwi->locker.lock();
        rwi->rwfalg = 1;
        --rwi->rWaiting;
        ++rwi->rRunning;
        long rWait = rwi->rWaiting;
        rwi->rWaiting = 0;
        for ( long i = 0; i < rWait; ++i)
            sem.post();
        rwi->locker.unlock();
    }
#else
    pthread_rwlock_rdlock( &(rwi->rwlock));
#endif
}

void RWLock::tryRDLock() {
    IMPL_VAR( rwi);
#ifdef M_WIN
    rwi->locker.lock();
    if ( rwi->rwflag == 0) {
        rwi->rwflag = 1;
        ++rwi->rRunning;
        rwi->locker.unlock();
        return true;
    }
    else if ( rwi->rwflag == 1) {
        ++rwi->rRunning;
        rwi->locker.unlock();
        return true;
    }
    else {  // == 2
        ++rwi->rWaiting;
        rwi->locker.unlock();
        rwi->sem.wait();
        rwi->locker.lock();
        rwi->rwfalg = 1;
        --rwi->rWaiting;
        ++rwi->rRunning;
        long rWait = rwi->rWaiting;
        rwi->rWaiting = 0;
        for ( long i = 0; i < rWait; ++i)
            sem.post();
        rwi->locker.unlock();
    }
#else
    if ( pthread_rwlock_tryrdlock( &(rwi->rwlock)) == 0)
        return true;
    else
        return false;
#endif
}

void RWLock::wrLock() {
    IMPL_VAR( rwi);
#ifdef M_WIN
    rwi->locker.lock();
    if ( rwi->rwflag == 0) {
        rwi->rwfalg = 2;
        rwi->locker.unlock();
        return;
    }
    else {  // == 1 || == 2
        ++rwi->wWaiting;
        rwi->locker.unlock();
        rwi->sem.wait();
        rwi->locker.lock();
        rwi->rwfalg = 2;
        --rwi->wWaiting;
        return;
    }
#else
    pthread_rwlock_wrlock( &(rwi->rwlock));
#endif
}

void RWLock::unlock() {
    IMPL_VAR( rwi);
#ifdef M_WIN
    SpinLock::Guard gl( rwi->locker);
    if ( rwi->rwflag == 1) {
        --rwi->rRunning;
        if ( rwi->rRunning == 0) {
            rwi->rwflag = 0;
            if ( rwi->rWaiting > 0 || rwi->wWaiting > 0)
                rwi->sem.post();
        }
    }
    else if ( rwi->rwflag == 2) {
        rwi->rwflag = 0;
        if ( rwi->rWaiting > 0 || rwi->wWaiting > 0)
            rwi->sem.post();
    }
    else
        assert( false);
#else
    pthread_rwlock_unlock( &(rwi->rwlock);
#endif
}

}   // namespace mix
